package de.mmis.core.pojop.server;

import java.io.IOException;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Queue;

import org.apache.log4j.Logger;

import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.LeafNode.Encoding;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.Tree.Type;
import de.mmis.core.base.abstracttree.TreeParserException;
import de.mmis.core.base.event.Observable;
import de.mmis.core.base.event.Observer;
import de.mmis.core.pojop.processor.RequestProcessorFactory;
import de.mmis.core.pojop.processor.RequestProcessorFactory.RequestProcessor;
import de.mmis.core.pojop.processor.RequestProcessorFactory.RequestProcessorEvent;
import de.mmis.core.pojop.request.AbstractRequest;
import de.mmis.core.pojop.request.AbstractRequest.RequestType;
import de.mmis.core.sexpression.SExpressionParser;

/**
 * implementation of the server interface providing a raw socket based
 * communication using S-Expressions intended to be used by other programs or
 * manually with netcat
 * 
 * @author Sebastian Bader
 * @author Christian Koch
 */
public class SocketServer extends AbstractServer<AbstractRequest> {

	private final static Logger LOGGER = Logger.getLogger(SocketServer.class);

	public SocketServer(int port,
			RequestProcessorFactory<AbstractRequest> requestProcessorFactory)
			throws IOException {
		super(port, requestProcessorFactory);
	}

	@Override
	public AbstractServer.SessionHandler createSessionHandler(Socket socket) {
		return new SocketSessionHandler(socket,
				this.requestProcessorFactory.createRequestProcessor());
	}

	public class SocketSessionHandler extends AbstractSessionHandler implements
			Observer<RequestProcessorEvent> {
		private Queue<AbstractRequest> requestQueue;

		public SocketSessionHandler(Socket socket,
				RequestProcessor<AbstractRequest> requestProcessor) {
			super(socket, requestProcessor);

			this.requestQueue = new LinkedList<AbstractRequest>();

			this.requestProcessor.addObserver(this);
		}

		@Override
		public AbstractRequest nextRequest() {

			// process queued requests if any
			if (!this.requestQueue.isEmpty())
				return this.requestQueue.remove();

			try {
				Tree exp = SExpressionParser.parse(this.instream, true);

				return nextRequest(exp);
			} catch (TreeParserException e) {
				if (e.cleanEndOfStreamOccurred()) {
					this.keepAnswering = false;
					return new AbstractRequest(
							RequestType.Echo,
							new LeafNode(
									"warning: closing connection due to end of input stream"));
				}
				return new AbstractRequest(e);
			} catch (IOException e) {
				LOGGER.error("IO error in client socket " + socket, e);
				return null;
			}
		}

		protected AbstractRequest nextRequest(Tree exp) {
			if (exp.getType() == Type.LEAF
					&& ((LeafNode) exp).equalsUTF8String(":help"))
				return new AbstractRequest(RequestType.Help, null);
			if (exp.getType() == Type.LEAF
					&& ((LeafNode) exp).equalsUTF8String(":methods"))
				return new AbstractRequest(RequestType.ListMethods, null);
			if (exp.getType() == Type.LEAF
					&& ((LeafNode) exp).equalsUTF8String(":properties"))
				return new AbstractRequest(RequestType.Properties, null);
			if (exp.getType() == Type.LEAF
					&& ((LeafNode) exp).equalsUTF8String(":type"))
				return new AbstractRequest(RequestType.Type, null);
			if (exp.getType() == Type.LEAF
					&& ((LeafNode) exp).equalsUTF8String(":interfaces"))
				return new AbstractRequest(RequestType.Interfaces, null);
			if (exp.getType() == Type.LEAF
					&& ((LeafNode) exp).equalsUTF8String(":quit"))
				return new AbstractRequest(RequestType.Quit, null);
			if (exp.getType() == Type.LEAF
					&& ((LeafNode) exp).equalsUTF8String(":observe"))
				return new AbstractRequest(RequestType.Observe, null);
			if (exp.getType() == Type.LEAF
					&& ((LeafNode) exp).equalsUTF8String(":deobserve"))
				return new AbstractRequest(RequestType.Deobserve, null);
			if (exp.getType() == Type.LEAF
					&& ((LeafNode) exp).equalsUTF8String(":jar-data"))
				return new AbstractRequest(RequestType.JarData, null);

			if (exp.getType() == Type.INNER
					&& ((InnerNode) exp).getNumberOfSubTrees() == 2
					&& ((InnerNode) exp).getSubTree(0).getType() == Type.LEAF
					&& ((LeafNode) ((InnerNode) exp).getSubTree(0))
							.getEncoding() == Encoding.Token
					&& ((LeafNode) ((InnerNode) exp).getSubTree(0))
							.equalsUTF8String(":help"))
				return new AbstractRequest(RequestType.Help,
						((InnerNode) exp).getSubTree(1));

			if (exp.getType() == Type.INNER
					&& ((InnerNode) exp).getNumberOfSubTrees() > 0
					&& ((InnerNode) exp).getSubTree(0).getType() == Type.LEAF
					&& ((LeafNode) ((InnerNode) exp).getSubTree(0))
							.getEncoding() == Encoding.Token
					&& ((LeafNode) ((InnerNode) exp).getSubTree(0))
							.equalsUTF8String(":observe")) {
				for (int i = 2; i < ((InnerNode) exp).getNumberOfSubTrees(); i++)
					this.requestQueue.add(new AbstractRequest(
							RequestType.Observe, ((InnerNode) exp)
									.getSubTree(i)));
				return new AbstractRequest(RequestType.Observe,
						((InnerNode) exp).getSubTree(1));
			}
			if (exp.getType() == Type.INNER
					&& ((InnerNode) exp).getNumberOfSubTrees() > 0
					&& ((InnerNode) exp).getSubTree(0).getType() == Type.LEAF
					&& ((LeafNode) ((InnerNode) exp).getSubTree(0))
							.getEncoding() == Encoding.Token
					&& ((LeafNode) ((InnerNode) exp).getSubTree(0))
							.equalsUTF8String(":deobserve")) {
				for (int i = 2; i < ((InnerNode) exp).getNumberOfSubTrees(); i++)
					this.requestQueue.add(new AbstractRequest(
							RequestType.Deobserve, ((InnerNode) exp)
									.getSubTree(i)));
				return new AbstractRequest(RequestType.Deobserve,
						((InnerNode) exp).getSubTree(1));
			}

			return new AbstractRequest(RequestType.MethodCall, exp);
		}

		@Override
		public void notify(Observable<? extends RequestProcessorEvent> sender,
				RequestProcessorEvent event) {
			try {
				outwrite(event.getAnswer());
			} catch (IOException e) {
				LOGGER.error("IO error in client socket", e);
				this.keepAnswering = false;
			}
		}

		@Override
		public String getConnectionTypeName() {
			return "Socket";
		}
	}
}
